package scanner;

import java.io.IOException;
import java.io.Reader;
import java.util.HashMap;
import java.util.Map;
import java.util.Observable;

public class Scanner
    extends Observable
{
    private String IDENTIFIER_REGEX_PATTERN = "[a-zA-Z0-9_]";

    private Reader inputReader;
    private SetOfSymbols setOfSymbols = new SetOfSymbols();
    private SetOfKeywords setOfKeywords = new SetOfKeywords();

    private Character currentChar;
    private Character nextChar;
    private boolean isEof = false;
    private int currentLine = 1;
    private int currentCol = -1;


    private Token nextToken = null;

    public Scanner(Reader reader)
    {
        inputReader = reader;
        nextChar = currentChar = ' ';
    }

    public Token Read()
    {
        if (nextToken == null)
        {
            nextToken = GetNextToken();
        }
        Token token = nextToken;
        nextToken = GetNextToken();
        return token;
    }

    public Token Peek()
    {
        return nextToken;
    }

    public int GetCurrentLine()
    {
        return currentLine;
    }

    public String GetTokenAsString(TokenId tokenId)
    {
        String tokenStr = "";
        Character tokenCh = setOfSymbols.GetSymbol(tokenId);
        if (tokenCh != null)
        {
            tokenStr = tokenCh.toString();
        }
        String keyword = setOfKeywords.GetKeyword(tokenId);
        if (keyword != null)
        {
            tokenStr = keyword;
        }
        return tokenStr;
    }

    public int GetCurrentCol()
    {
        return currentCol;
    }

    public Token GetNextToken()
    {
        Token token = null;
        while (token == null)
        {
            if (isEof)
            {
                return new Token(TokenId.EOF);
            }

            if (Character.isWhitespace(currentChar))
            {
                SkipWhitespaces();
            }
            else if (Character.isAlphabetic(currentChar))
            {
                token = ReadAlphabeticToken();
            }
            else if (Character.isDigit(currentChar))
            {
                token = ReadNumericToken();
            }
            else if (setOfSymbols.containsKey(currentChar))
            {
                token = ReadSymbolicToken();
            }
            else if (currentChar == '&')
            {
                token = ReadLogicalAndToken();
            }
            else if (currentChar == '/')
            {
                token = ReadCommentToken();
            }
            else
            {
                ProcessError();
            }
        }
        return token;
    }

    private Token ReadSymbolicToken()
    {
        Token token = new Token(setOfSymbols.get(currentChar));
        ReadChar();
        return token;
    }

    private Token ReadAlphabeticToken()
    {
        String word = ReadWord();
        TokenId tokenId = (setOfKeywords.containsKey(word)) ? setOfKeywords.get(word) : TokenId.IDENTIFIER;

        return new Token(tokenId, word);
    }

    private Token ReadNumericToken()
    {
        Token token = null;
        String numericString = ReadNumericString();
        if (!numericString.isEmpty())
        {
            int tokenData = Integer.parseInt(numericString);
            token = new Token(TokenId.INTEGER_LITERAL, tokenData);
        }
        return token;
    }

    private Token ReadLogicalAndToken()
    {
        Token token = null;
        if (nextChar == '&')
        {
            token = new Token(TokenId.AND);
            ReadChar();
            ReadChar();
        }
        else
        {
            ProcessError('&');
        }
        return token;
    }

    private Token ReadCommentToken()
    {
        Token token = null;
        if (nextChar == '/')
        {
            String data = ReadSingleLineComments();
            token = new Token(TokenId.COMMENT, data);
        }
        else if (nextChar == '*')
        {
            String data = ReadMultiLineComments();
            token = new Token(TokenId.COMMENT, data);
        }
        else
        {
            ProcessError('/');
        }
        return token;
    }

    private void ReadChar()
    {
        try
        {
            currentChar = nextChar;
            if (currentChar != Character.MAX_VALUE)
            {
                nextChar = ReadNextChar();
            }
            else
            {
                isEof = true;
            }
        }
        catch (IOException ex)
        {
            System.out.println(ex.getMessage());
            isEof = true;
        }
    }

    private Character ReadNextChar()
        throws IOException
    {
        Character newChar = Character.MAX_VALUE;
        int ch = inputReader.read();
        IncreaseCurrentCaretPosition();
        if (ch != -1)
        {
            newChar = (char) ch;
        }
        return newChar;
    }

    private void IncreaseCurrentCaretPosition()
    {
        currentCol++;
        if (currentChar == Character.LINE_SEPARATOR)
        {
            currentLine++;
            currentCol = -1;
        }
    }

    private void ProcessError(Character unexpectedChar)
    {
        setChanged();
        notifyObservers("Scan error(" + currentLine + ", " + currentCol + "): Unexpected symbol " + unexpectedChar);
        ReadChar();
    }

    private void ProcessError()
    {
        ProcessError(currentChar);
    }

    private String ReadNumericString()
    {
        String numericString = "";
        while (Character.isDigit(currentChar))
        {
            numericString += currentChar;
            ReadChar();
        }
        if (Character.isAlphabetic(currentChar))
        {
            ProcessError();
            numericString = "";
        }
        return numericString;
    }

    private String ReadWord()
    {
        String word = "";
        while (currentChar.toString().matches(IDENTIFIER_REGEX_PATTERN))
        {
            word += currentChar;
            ReadChar();
        }
        return word;
    }

    private String ReadSingleLineComments()
    {
        String commentString = "";
        while (currentChar != Character.LINE_SEPARATOR && !isEof)
        {
            commentString += currentChar;
            ReadChar();
        }
        return commentString;
    }

    private String ReadMultiLineComments()
    {
        String commentString = "";
        while (!(currentChar == '*' && nextChar == '/') && !isEof)
        {
            commentString += currentChar;
            ReadChar();
        }
        commentString += "*/";
        ReadChar();
        ReadChar();
        return commentString;
    }

    private void SkipWhitespaces()
    {
        while (Character.isWhitespace(currentChar))
        {
            ReadChar();
        }
    }

}
